import java.io.*;
import java.lang.System;
import java.util.concurrent.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

/********************
 * Receiver.java
 * -----------------
 * v1.0
 * Date: 1/23/2010
 * Description:
 * Receive packets from various relays and sort them into a rcvBuffer, 
 * which then passes packets to the de-diversification code.
 * Last Back-Up: None, first revision
 * Revision History: None
 * 
 * Resources:
 * http://java.sun.com/docs/books/tutorial/essential/concurrency/index.html
 * 
 * @author Grace
 ********************/

public class Receiver {
	DatagramPacket p;
	DatagramSocket rxsock;
	int destPort;
	int windowIndex;
	int windowSize = 5;
	int rcvBufferSize = 1000;
	int seqNum; 
	int pktType;
	long timeout = 10; //in miliseconds
	int maxPacketSize = 70;
	byte[][][] rcvBuffer;
	byte[] data;
	
	public void main(String args[]){
		//data is a 1D byte array to store the data payload of each packet temporarily
		data = new byte[maxPacketSize - 2];
		
		//rcvBuffer is a 3D byte array used to sort incoming packets before passing them
		//to the de-diversification program
		rcvBuffer = new byte[rcvBufferSize][3][maxPacketSize - 2];
		
		//Initialize a socket rxsock and bind with destination port destPort
		try{
			rxsock = new DatagramSocket(destPort);
			System.out.println("Initializing socket.");
		}catch(Exception e){
			System.out.println("Failed to open socket.");
		}
		
		//Initialize sliding window index for rcvBuffer
		windowIndex = 0;
		
		//Start thread to receive packets p and place them in rcvBuffer if valid
		//i.e. if the packet's seq # is w/in the range of the sliding window
		ReceiveThread rcvThread = new ReceiveThread();
		
		//Start thread to manage sliding window algorithm for rcvBuffer
		SlidingWindowThread slideWin = new SlidingWindowThread();
		
	}
	
	
	//ReceiveThread private class
	//Function: Receive packets p and place them in rcvBuffer if valid
	//i.e. if the packet's seq # is w/in the range of the sliding window
	private class ReceiveThread implements Runnable {
		byte[] buff = new byte[maxPacketSize-2];
		
		public void run(){
			//Receive a packet
			while(true){
				//Receive a DatagramPacket p through DatagramSocket rxsock 
				try{
					rxsock.receive(p);
				}catch(IOException ioe){
					System.out.println("IOException during receiving packets.");
				}
				
				/*Format of packet received at receiver side:
				 * <seq num (1 byte)> <pkt type (1 byte)> <payload> */
				
				//Check packet seq num
				//byte[] buff is the temporary buffer to store the payload of UDP packet
				buff = p.getData();
				seqNum = buff[0];
				//If packet received is within sliding window range, 
				//then store packet data in rcvBuffer
				if((seqNum >= windowIndex) && (seqNum < (windowIndex + windowSize))){
					pktType = buff[1];
					//Strip VIPER header off UDP packet payload and store voice data in byte[] data
					System.arraycopy(buff,2,data,0,buff.length-2);
					switch (pktType){
						case 0: rcvBuffer[seqNum][0] = data; break;
						case 1: rcvBuffer[seqNum][1] = data; break;
						case 2: rcvBuffer[seqNum][2] = data; break;
						default: break;	
					}
				}
			}
		}
		
		public void main(String args[]){
			(new Thread(new ReceiveThread())).start();
		}
	}

	//SlidingWindowThread private class
	//Function: Manage sliding window algorithm for rcvBuffer
	//1. windowIndex is first window in range of sliding window
	//2. When first window is filled sufficiently (2/3 packets), send to de-diversification program
	//3. If first window timeouts, drop packet 
	private class SlidingWindowThread implements Runnable {
		long startTime, endTime;
		int startIndex;
		
		public void run(){
			while(true){
				startIndex = windowIndex;
				startTime = System.currentTimeMillis();
				endTime = startTime + timeout;
				boolean b = false; 
				
				//Implement timeout on first window
				while (endTime >= System.currentTimeMillis()) {
					
					//Packet A and B are received
					if((rcvBuffer[windowIndex][0]!=null)&&(rcvBuffer[windowIndex][1]!=null))
						b = true;
					//Packet A and A^B are received
					else if((rcvBuffer[windowIndex][0]!=null)&&(rcvBuffer[windowIndex][2]!=null))
						b = true;
					//Packer B and A^B are received
					else if((rcvBuffer[windowIndex][1]!=null)&&(rcvBuffer[windowIndex][2]!=null))
						b = true;
					
					//If window is "full enough", then
					if(b){
						//Send packet to de-diversification program
						//send(windowIndex)
						windowIndex++;
						
						//Wrap around window index #s id rcvBuffer is full
						if(windowIndex == rcvBufferSize)
							windowIndex = 0;
						
						break;
					}
				}
				
				//If first window timeouts, drop packet
				if(startIndex == windowIndex)
					windowIndex++;
				
				//Wrap around window index #s id rcvBuffer is full
				if(windowIndex == rcvBufferSize)
					windowIndex = 0;
			}
		}
		
		public void main(String args[]){
			(new Thread(new SlidingWindowThread())).start();
		}
	}
}
